/**
 * dxVgBle Module.
 * - UART based communication for VgBle (Bluetooth) device.
 * - Supports reading device config (getConfig) and pushing config (setConfig).
 * - Parses common protocol frames (55AA...) and inner BLE-specific payloads (P1=7E/7A).
 * - Exposes a loop method to be polled by upper layer.
 *
 * Design notes:
 * - All binary data is represented as hex uppercase string when crossing the JS boundary.
 * - This module is protocol-focused and keeps state in `dxMap("dxVgBleMap")` so it can be shared across workers.
 * - Public APIs are exposed via `dxVgBle.slave` and `dxVgBle.loop`.
 */
import dxCommonUtils from './dxCommonUtils.js'
import dxChannel from './dxChannel.js'
import dxLogger from './dxLogger.js'
import dxDriver from './dxDriver.js'
import dxMap from './dxMap.js'
import dxStd from './dxStd.js'
import dxOs from './dxOs.js'

const dxVgBle = {}

let handle_id

// Global debug switch for all dxVgBle logs (controlled by init debug parameter)
let g_debug = false

/**
 * Internal log wrapper. All dxVgBle logs should go through this function
 * so that they can be globally enabled/disabled by `init(debug)`.
 * @param {...any} args - arguments forwarded to dxLogger.info
 */
function logInfo() {
    if (!g_debug) {
        return;
    }
    dxLogger.info.apply(dxLogger, arguments);
}

// Global callbacks for BLE events
let g_callbacks = {
    onMessage: null,
    onConnectSuccess: null
}

/**
 * Initialize UART channel and store handle into dxMap (idempotent across threads).
 * Upper layer should call this once before using any `dxVgBle.slave.*` APIs.
 * @param {string} [rate] UART config string, for example '115200-8-N-1'.
 * @param {boolean} [debug=false] When true, enable all dxVgBle logs; when false, suppress all logs.
 */
dxVgBle.init = function (rate = '115200-8-N-1', debug = false) {
    // Always update debug flag even if already inited, so upper layer can toggle logging.
    g_debug = !!debug

    const vgMap = dxMap.get("dxVgBleMap")
    const inited = vgMap.get("inited")
    if (inited === true) {
        return;
    }
    handle_id = dxChannel.open(dxChannel.TYPE.UART, dxDriver.CHANNEL.BLE_PATH)
    dxChannel.ioctl(handle_id, dxChannel.IOC_SET_CMD.CHANNEL_IOC_SET_UART_PARAM, rate)
    vgMap.put("handle_id", handle_id)
    vgMap.put("inited", true)
}

// Public BLE slave operations (high-level API for upper layers)
dxVgBle.slave = {
    /**
     * Send BLE config to device.
     * Params accept hex strings; empty values are skipped.
     * Returns a Promise resolved by async setConfig response (P1=7A).
     *
     * Protocol:
     *   55 AA 60 00 LL HH [P1=7A] [TLVs...] FE BCC
     *
     * @param {string} key - auth key in hex string, used for later ciphertext verification.
     * @param {object} [params] - optional, { nameHex, macHex, snHex, deviceStatusHex } hex strings.
     * @returns {Promise<any>} resolved when setConfig response arrives or timeout.
     */
    setConfig: function (key, params = {}) {
        checkHandleId()

        const vgMap = dxMap.get("dxVgBleMap");

        // Store auth key into vgMap for later use in cmd08 verification.
        // Expect key to be a hex string.
        if (key && typeof key === 'string') {
            vgMap.put("auth_key_hex", key.toUpperCase())
        }

        const { nameHex, macHex, snHex, deviceStatusHex } = params
        const hasConfig = !!(nameHex || macHex || snHex || deviceStatusHex);

        // If params is empty (no config fields), only store key and return.
        if (!hasConfig) {
            return Promise.resolve();
        }

        function makeTlv(tHex, vHex) {
            if (!vHex) {
                return '';
            }
            const safeV = vHex.toUpperCase();
            const lenByte = Math.floor(safeV.length / 2);
            const lenHex = lenByte.toString(16).padStart(2, '0');
            return tHex + lenHex + safeV;
        }

        const nameTlv = makeTlv("10", nameHex);
        const macTlv = makeTlv("11", macHex);
        const snTlv = makeTlv("14", snHex);
        const statusTlv = makeTlv("15", deviceStatusHex);

        const payloadHex = "7a00" + nameTlv + macTlv + snTlv + statusTlv + "fe";
        const payloadInfo = buildPayloadLength(payloadHex);
        const finalContent = buildAndSendFrame(0x60, 0x00, payloadHex, 'dxVgBle TX: slave.setConfig', payloadInfo);
        const token = Date.now().toString();

        vgMap.put("slave_setConfig_token", token);
        vgMap.del("slave_setConfig_result");

        return new Promise((res, rej) => {
            const start = Date.now();
            const intervalId = dxStd.setInterval(() => {
                const result = vgMap.get("slave_setConfig_result");
                if (result && result.token === token) {
                    dxStd.clearInterval(intervalId);
                    vgMap.del("slave_setConfig_result");
                    res(result.semantic || result);
                    return;
                }
                if (Date.now() - start > 5000) {
                    dxStd.clearInterval(intervalId);
                    rej(new Error("dxVgBle.slave: setConfig timeout."));
                }
            }, 50);
        })
    },
    /**
     * Request BLE config from device.
     * Returns a Promise resolved by async getConfig response (P1=7E cmd=0x02).
     *
     * Protocol:
     *   55 AA 60 00 06 00 7E 01 00 02 00 FE BCC
     *
     * @returns {Promise<any>} resolved with semantic config or timeout error.
     */
    getConfig: async function () {
        checkHandleId()
        const getCfgFrame = new Uint8Array([
            0x55, 0xaa, 0x60, 0x00, 0x06, 0x00,
            0x7e, 0x01, 0x00, 0x02, 0x00, 0xfe, 0x9c
        ])
        logInfo('dxVgBle TX: slave.getConfig', bytesToHex(getCfgFrame))
        dxChannel.send(handle_id, getCfgFrame.buffer)
        const token = Date.now().toString()
        const vgMap = dxMap.get("dxVgBleMap")
        vgMap.put("slave_getConfig_token", token)
        vgMap.del("slave_getConfig_result")

        return new Promise((res, rej) => {
            const start = Date.now()
            const intervalId = dxStd.setInterval(() => {
                const result = vgMap.get("slave_getConfig_result")
                if (result && result.token === token) {
                    dxStd.clearInterval(intervalId)
                    vgMap.del("slave_getConfig_result")
                    res(result.semantic || result)
                    return
                }
                if (Date.now() - start > 5000) {
                    dxStd.clearInterval(intervalId)
                    rej(new Error("dxVgBle.slave: getConfig timeout."))
                }
            }, 50)
        })
    },
    /**
     * Register callbacks from upper layer.
     * - `onMessage(dataHex)` is triggered when transparent data (cmd=0x0F) is received.
     * - `onConnectSuccess(connId)` is triggered when AES verification in cmd08 succeeds.
     * @param {object} callbacks
     */
    setCallbacks: function (callbacks) {
        if (!callbacks || typeof callbacks.onMessage !== 'function' || typeof callbacks.onConnectSuccess !== 'function') {
            throw new Error('Callbacks must be an object with onMessage and onConnectSuccess functions');
        }

        g_callbacks.onMessage = callbacks.onMessage;
        g_callbacks.onConnectSuccess = callbacks.onConnectSuccess;
    },
    /**
     * Send transparent data to remote side (cmd = 0x0F).
     * Message is application data in hex string; `connId` is the BLE connection identifier.
     * @param {string} message - application data in hex string (without connId).
     * @param {number} connId - connection identifier (0-255).
     */
    sendMessage: function (message, connId) {
        const payloadHex = message + connId.toString(16).padStart(2, '0');
        const payloadInfo = buildPayloadLength(payloadHex);
        buildAndSendFrame(0x0f, 0x00, payloadHex, 'dxVgBle TX: cmd0f', payloadInfo);
    }
}

/**
 * Polling entry to process incoming UART frames.
 * Should be called periodically by upper layer main loop / timer.
 */
dxVgBle.loop = function () {
    vgCommonProtocol();
}

/**
 * Uint8Array -> hex uppercase string.
 * @param {Uint8Array} uint8Arr
 * @returns {string}
 */
function bytesToHex(uint8Arr) {
    if (!uint8Arr) {
        return '';
    }
    let hex = '';
    for (let i = 0; i < uint8Arr.length; i++) {
        const h = uint8Arr[i].toString(16).padStart(2, '0');
        hex += h;
    }
    return hex.toUpperCase();
}

/**
 * Hex string -> Uint8Array.
 * @param {string} hexStr
 * @returns {Uint8Array}
 */
function hexToBytes(hexStr) {
    if (!hexStr || typeof hexStr !== 'string') {
        return new Uint8Array(0);
    }
    const cleanHex = hexStr.trim();
    if (cleanHex.length % 2 !== 0) {
        return new Uint8Array(0);
    }
    const len = cleanHex.length / 2;
    const arr = new Uint8Array(len);
    for (let i = 0; i < len; i++) {
        const byteHex = cleanHex.substr(i * 2, 2);
        arr[i] = parseInt(byteHex, 16);
    }
    return arr;
}

/**
 * Ensure handle_id is available (fetch from dxMap if needed).
 * This makes sure APIs can work after cross-thread calls where the local variable was not initialized.
 */
function checkHandleId() {
    if (handle_id == null || handle_id == undefined) {
        handle_id = dxMap.get("dxVgBleMap").get("handle_id")
    }
}

/**
 * Pre-calc payload length and its low/high bytes from a payload hex string.
 * @param {string} payloadHex
 * @returns {{payloadLen:number,lenLowHex:string,lenHighHex:string}}
 */
function buildPayloadLength(payloadHex) {
    const payloadLen = Math.floor((payloadHex ? payloadHex.length : 0) / 2);
    const lenLowHex = (payloadLen & 0xff).toString(16).padStart(2, '0');
    const lenHighHex = ((payloadLen >> 8) & 0xff).toString(16).padStart(2, '0');
    return {
        payloadLen: payloadLen,
        lenLowHex: lenLowHex,
        lenHighHex: lenHighHex
    };
}

/**
 * Build full 55AA frame, calculate checksum and send through UART.
 * Caller is responsible for making sure handle_id is ready.
 * @param {number} cmd - command byte.
 * @param {number} result - result byte (0x00 normally).
 * @param {string} payloadHex - payload hex string.
 * @param {string} logPrefix - logger prefix string.
 * @param {{payloadLen:number,lenLowHex:string,lenHighHex:string}} [payloadInfo] - optional pre-calculated length info.
 * @returns {string} final hex content that has been sent.
 */
function buildAndSendFrame(cmd, result, payloadHex, logPrefix, payloadInfo) {
    checkHandleId();
    if (handle_id == null || handle_id == undefined) {
        return '';
    }

    const cmdHex = cmd.toString(16).padStart(2, '0');
    const resultHex = result.toString(16).padStart(2, '0');
    const info = payloadInfo || buildPayloadLength(payloadHex || '');

    // full content without checksum
    const content = "55aa" + cmdHex + resultHex + info.lenLowHex + info.lenHighHex + (payloadHex || '');

    // calc bcc over payload
    const payloadBytes = payloadHex ? hexToBytes(payloadHex) : new Uint8Array(0);
    const checksum = blebcc(info.payloadLen, cmd, result, payloadBytes);
    const checksumHex = checksum.toString(16).padStart(2, '0');

    const finalContent = (content + checksumHex).toUpperCase();

    logInfo(logPrefix, finalContent);
    const sendBuf = hexToBytes(finalContent);
    dxChannel.send(handle_id, sendBuf.buffer);

    return finalContent;
}

/**
 * Common protocol handler: reads one frame from UART, validates BCC, dispatches to vgBleProtocol.
 */
function vgCommonProtocol() {
    checkHandleId();
    if (handle_id == null || handle_id == undefined) {
        return;
    }

    // Read header (2 bytes)
    const header = dxChannel.receive(handle_id, 2, 100);
    if (header === null || header.length !== 2) {
        return;
    }

    if (header[0] !== 0x55 || header[1] !== 0xAA) {
        // Not a valid header, ignore this byte sequence
        return;
    }

    // Read cmd (1 byte)
    const cmdBuf = dxChannel.receive(handle_id, 1, 100);
    if (cmdBuf === null || cmdBuf.length !== 1) {
        return;
    }
    const cmd = cmdBuf[0];

    // Read length (2 bytes, little-endian)
    const lenBuf = dxChannel.receive(handle_id, 2, 100);
    if (lenBuf === null || lenBuf.length !== 2) {
        return;
    }
    const length = lenBuf[0] | (lenBuf[1] << 8);

    // Read payload (length bytes)
    let payload = null;
    if (length > 0) {
        payload = dxChannel.receive(handle_id, length, 100);
        if (payload === null || payload.length !== length) {
            return;
        }
    } else {
        payload = new Uint8Array(0);
    }

    // Read checksum (1 byte)
    const checksumBuf = dxChannel.receive(handle_id, 1, 100);
    if (checksumBuf === null || checksumBuf.length !== 1) {
        return;
    }
    const checksum = checksumBuf[0];

    // Build whole packet buffer for logging
    const totalLen = 2 + 1 + 2 + payload.length + 1;
    const packetBuf = new Uint8Array(totalLen);
    packetBuf[0] = header[0];
    packetBuf[1] = header[1];
    packetBuf[2] = cmd;
    packetBuf[3] = lenBuf[0];
    packetBuf[4] = lenBuf[1];
    if (payload.length > 0) {
        packetBuf.set(payload, 5);
    }
    packetBuf[totalLen - 1] = checksum;

    const packet = {
        header: '55AA',
        cmd: cmd,
        length: length,
        payloadHex: bytesToHex(payload),
        checksum: checksum,
        rawHex: bytesToHex(packetBuf)
    };

    if (blebcc(length, cmd, 0, payload) == checksum) {
        logInfo('vgCommonProtocol packet:', JSON.stringify(packet));
        vgBleProtocol(packet);
    } else {
        throw new Error('vgCommonProtocol: bcc check failed');
    }
}

/**
 * Calculate BCC for payload.
 * @param {number} dlen - payload length
 * @param {number} cmd
 * @param {number} result
 * @param {Uint8Array} data
 * @returns {number}
 */
function blebcc(dlen, cmd, result, data) {
    let bcc = 0;
    bcc ^= 0x55;
    bcc ^= 0xaa;
    bcc ^= cmd;
    bcc ^= result;
    bcc ^= (dlen - 1 & 0xff);
    bcc ^= (dlen - 1 & 0xff00) >> 8;
    for (let i = 0; i < dlen; i++) {
        bcc ^= data[i];
    }
    return bcc;
}

/**
 * Parse TLV buffer. Each item format: T(1 byte) L(1 byte) V(L bytes).
 * @param {Uint8Array} dataBytes
 * @returns {Array<{t:number,length:number,valueHex:string,value:number[]}>}
 */
function parseTlvs(dataBytes) {
    const tlvs = [];
    if (!dataBytes || !dataBytes.length) {
        return tlvs;
    }

    let offset = 0;
    while (offset + 2 <= dataBytes.length) {
        const t = dataBytes[offset];
        const l = dataBytes[offset + 1];
        if (offset + 2 + l > dataBytes.length) {
            break;
        }
        const vBytes = dataBytes.slice(offset + 2, offset + 2 + l);
        tlvs.push({
            t: t,
            length: l,
            valueHex: bytesToHex(vBytes),
            value: Array.from(vBytes)
        });
        offset += 2 + l;
    }
    return tlvs;
}

// VgBle protocol parser (handles BLE-specific payload inside common 0x60/0x07/0x08/0x0F frames)
/**
 * Parse inner BLE protocol (payload of common frame).
 * Dispatch to specific command handlers by cmd/P1.
 * @param {object} pack - { header, cmd, length, payloadHex, checksum, rawHex }
 */
function vgBleProtocol(pack) {
    if (!pack || typeof pack.payloadHex !== 'string') {
        return;
    }

    switch (pack.cmd.toString(16).padStart(2, '0')) {
        case '60':
            cmd60(pack)
            break;
        case '07':
            cmd07(pack)
            break;
        case '08':
            cmd08(pack)
            break;
        case '0f':
            cmd0f(pack)
            break;
        default:
            break;
    }
}

function getUrandom(len) {
    return dxOs.systemWithRes(`dd if=/dev/urandom bs=1 count="${len}" 2>/dev/null | xxd -p`, 100).split(/\s/g)[0]
}

function cmd07(pack) {
    if (!pack || !pack.payloadHex) {
        return;
    }

    const payloadBytes = hexToBytes(pack.payloadHex);
    let connId;
    let randomBits = 16; // default 16 bits

    if (pack.length === 1) {
        // 1 byte: connection identifier only
        connId = payloadBytes[0];
    } else if (pack.length === 2) {
        // 2 bytes: first byte is P1 (random bits), second byte is connection identifier
        const P1 = payloadBytes[0];
        if (P1 === 16 || P1 === 32) {
            randomBits = P1;
        } else {
            // Invalid P1, use default 16 bits
            randomBits = 16;
        }
        connId = payloadBytes[1];
    } else {
        // Invalid length, ignore
        logInfo('cmd07: invalid payload length, expected 1 or 2 bytes, got', pack.length);
        return;
    }

    // Generate random number
    const randomHex = getUrandom(randomBits);

    // Get existing connections array from map, or initialize as empty array
    const vgMap = dxMap.get("dxVgBleMap")
    let connections = vgMap.get("cmd07_connections");
    if (!Array.isArray(connections)) {
        connections = [];
    }

    // Find existing connection with same connId
    let existingIndex = -1;
    for (let i = 0; i < connections.length; i++) {
        if (connections[i].connId === connId) {
            existingIndex = i;
            break;
        }
    }

    // Create connection object
    const connObj = {
        connId: connId,
        random: randomHex,
        randomBits: randomBits
    };

    // Update or add connection
    if (existingIndex >= 0) {
        // Update existing connection
        connections[existingIndex] = connObj;
        logInfo('cmd07: updated connection connId=', connId, 'random=', randomHex, 'randomBits=', randomBits);
    } else {
        // Add new connection
        connections.push(connObj);
        logInfo('cmd07: added new connection connId=', connId, 'random=', randomHex, 'randomBits=', randomBits);
    }

    // Store updated connections array to map
    vgMap.put("cmd07_connections", connections);

    const payloadHex = randomHex + connId.toString(16).padStart(2, '0');
    const payloadInfo = buildPayloadLength(payloadHex);
    buildAndSendFrame(0x07, 0x00, payloadHex, 'dxVgBle TX: cmd07', payloadInfo);
}

function cmd08(pack) {
    if (!pack || !pack.payloadHex) {
        return;
    }

    const payloadBytes = hexToBytes(pack.payloadHex);

    if (pack.length < 2) {
        logInfo('cmd08: invalid payload length, expected at least 2 bytes, got', pack.length);
        return;
    }

    // First byte is P1 (MTU), skip for now
    const P1 = payloadBytes[0];

    // Last byte is connection identifier
    const connId = payloadBytes[payloadBytes.length - 1];

    // Middle bytes are encrypted ciphertext
    const ciphertextBytes = payloadBytes.slice(1, payloadBytes.length - 1);
    const ciphertextHex = bytesToHex(ciphertextBytes);

    logInfo('cmd08: P1(MTU)=', P1, 'connId=', connId, 'ciphertextHex=', ciphertextHex);

    // Get connection info from map to find corresponding random number
    const vgMap = dxMap.get("dxVgBleMap");
    let connections = vgMap.get("cmd07_connections");
    if (!Array.isArray(connections)) {
        connections = [];
    }

    // Find connection by connId
    let connection = null;
    for (let i = 0; i < connections.length; i++) {
        if (connections[i].connId === connId) {
            connection = connections[i];
            break;
        }
    }

    if (!connection) {
        logInfo('cmd08: connection not found for connId=', connId);
        return;
    }

    logInfo('cmd08: found connection for connId=', connId, 'random=', connection.random);

    // Get auth key from map (value previously stored by slave.setConfig key parameter).
    const keyHex = vgMap.get("auth_key_hex");
    if (!keyHex || typeof keyHex !== 'string') {
        logInfo('cmd08: auth key not found in vgMap, cannot verify ciphertext. connId=', connId);
        return;
    }

    // Verify ciphertext: encrypt the random with the same AES-128-ECB(NoPadding) and compare with device ciphertext
    const randomHex = connection.random;
    const randomByteLen = randomHex ? Math.floor(randomHex.length / 2) : 0;
    if (!randomHex || randomByteLen === 0 || randomByteLen % 16 !== 0) {
        logInfo('cmd08: invalid random length for AES-ECB, randomHex=', randomHex);
        return;
    }

    const encryptedRandomBase64 = dxCommonUtils.crypto.aes.encrypt(
        hexToBytes(randomHex).buffer,
        hexToBytes(keyHex).buffer,
        {
            mode: dxCommonUtils.AES_MODE.ECB,
            keySize: dxCommonUtils.AES_KEY_SIZE.BITS_128,
            padding: dxCommonUtils.AES_PADDING.NONE
        }
    );

    if (encryptedRandomBase64 === null) {
        logInfo('cmd08: encrypt random failed for connId=', connId);
        return;
    }

    const encryptedRandomBuf = dxCommonUtils.codec.base64ToArrayBuffer(encryptedRandomBase64);
    const encryptedRandomHex = bytesToHex(new Uint8Array(encryptedRandomBuf));

    // Take first N hex chars (N = random length) and compare with ciphertext
    const compareLen = randomHex.length;
    const truncatedHex = encryptedRandomHex.slice(0, compareLen);
    const truncatedCipherHex = ciphertextHex.slice(0, compareLen);

    const verifyOk = truncatedHex.toUpperCase() === truncatedCipherHex.toUpperCase();
    connection.verifyOk = verifyOk;
    vgMap.put("cmd07_connections", connections);

    let result = 0x00;
    if (verifyOk) {
        result = 0x00;
        logInfo('cmd08: ciphertext verify success, connId=', connId);
        if (typeof g_callbacks.onConnectSuccess === 'function') {
            try {
                g_callbacks.onConnectSuccess(connId);
            } catch (e) {
                logInfo('cmd08: onConnectSuccess callback error:', e && e.message ? e.message : e);
            }
        }
    } else {
        result = 0x90;
        logInfo('cmd08: ciphertext verify failed, connId=', connId, 'randomHex=', randomHex, 'encryptedRandomHex=', encryptedRandomHex, 'truncatedHex=', truncatedHex);
    }

    // Only 1 byte payload: connId
    const connIdHex = connId.toString(16).padStart(2, '0');
    const payloadHex = connIdHex;

    // For cmd08, length is fixed to 1; we can pre-build length info.
    const payloadInfo = {
        payloadLen: 1,
        lenLowHex: '01',
        lenHighHex: '00'
    };

    buildAndSendFrame(0x08, result, payloadHex, 'dxVgBle TX: cmd08', payloadInfo);
}

function cmd0f(pack) {
    if (!pack || !pack.payloadHex) {
        return;
    }

    const hex = pack.payloadHex.toUpperCase();

    // Need at least P1 P2 P3 + connId = 4 bytes => 8 hex chars
    if (hex.length < 8) {
        logInfo('cmd0f: payload too short, hex =', hex);
        return;
    }

    const P1 = hex.slice(0, 2);
    const P2 = hex.slice(2, 4);
    const P3 = hex.slice(4, 6);
    const connIdHex = hex.slice(hex.length - 2);
    const dataHex = hex.slice(6, hex.length - 2); // transparent data
    const dataBytes = hexToBytes(dataHex);

    const transparentPacket = {
        P1: P1,
        P2: P2,
        P3: P3,
        connId: connIdHex,
        dataHex: dataHex,
        data: Array.from(dataBytes)
    };

    // Attach parsed 0F packet to pack for upper layer usage
    pack.transparent = transparentPacket;

    const vgMap = dxMap.get("dxVgBleMap");
    vgMap.put("last_cmd0f_packet", transparentPacket);

    if (typeof g_callbacks.onMessage === 'function') {
        try {
            g_callbacks.onMessage(dataHex);
        } catch (e) {
            logInfo('cmd0f: onMessage callback error:', e && e.message ? e.message : e);
        }
    }

    logInfo('cmd0f: parsed transparent packet:', JSON.stringify(transparentPacket));
}

function cmd60(pack) {
    const hex = pack.payloadHex.toUpperCase();
    // Need at least 3(P1,P2,P3)+1(cmd)+1(len)+1(conn_id) bytes = 6 bytes => 12 hex chars
    if (hex.length < 12) {
        return;
    }

    const P1 = hex.slice(0, 2);
    const P2 = hex.slice(2, 4);
    const P3 = hex.slice(4, 6);

    switch (P1) {
        case '7E':
            parseP1_7E(pack, hex, P2, P3);
            break;
        case '7A':
            parseP1_7A(pack, hex, P2);
            break;
        default:
            logInfo('vgBleProtocol: unsupported P1, ignore packet. P1 =', P1);
            break;
    }
}

/**
 * Parser for P1 = 0x7E (3-byte header P1/P2/P3, then cmd/len/data/connId).
 * @param {object} pack
 * @param {string} hex - payload hex
 * @param {string} P2
 * @param {string} P3
 */
function parseP1_7E(pack, hex, P2, P3) {
    const cmdHex = hex.slice(6, 8);
    const lenHex = hex.slice(8, 10);
    const cmd = parseInt(cmdHex, 16);
    const dataLen = parseInt(lenHex, 16);

    // 3(P1,P2,P3) + 1(cmd) + 1(len) + dataLen + 1(conn_id)
    const expectedTotalBytes = 3 + 1 + 1 + dataLen + 1;
    const expectedHexLen = expectedTotalBytes * 2;
    if (hex.length < expectedHexLen) {
        return;
    }

    const dataStart = 10;
    const dataEnd = dataStart + dataLen * 2;
    const dataHex = hex.slice(dataStart, dataEnd);
    const connIdHex = hex.slice(dataEnd, dataEnd + 2);

    const dataBytes = hexToBytes(dataHex);
    const tlvs = parseTlvs(dataBytes);

    // Semantic TLV mapping
    const semantic = {};
    for (let i = 0; i < tlvs.length; i++) {
        const item = tlvs[i];
        switch (item.t) {
            case 0x01:
                // Bluetooth name
                semantic.nameHex = item.valueHex;
                break;
            case 0x02:
                // Bluetooth MAC
                semantic.macHex = item.valueHex;
                break;
            case 0x03:
                // Serial number
                semantic.snHex = item.valueHex;
                break;
            case 0x04:
                // Device status in advertising
                semantic.deviceStatusHex = item.valueHex;
                break;
            default:
                break;
        }
    }

    const blePacket = {
        P1: '7E',
        P2: P2,
        P3: P3,
        cmd: cmd,
        length: dataLen,
        dataHex: dataHex,
        data: Array.from(dataBytes),
        connId: connIdHex,
        tlv: tlvs,
        semantic: semantic
    };

    // attach parsed ble packet into original vg packet for upper layer usage
    pack.ble = blePacket;
    logInfo('vgBleProtocol ble packet (P1=7E):', JSON.stringify(blePacket));

    // When P1 == 0x7E and inner cmd == 0x02, store semantic data for getConfig via dxMap
    if (cmd === 0x02) {
        const vgMap = dxMap.get("dxVgBleMap");
        const token = vgMap.get("slave_getConfig_token");
        vgMap.put("slave_getConfig_result", {
            token: token,
            semantic: semantic
        });
    }
}

/**
 * Parser for P1 = 0x7A (2-byte header P1/P2, then TLVs, then connId).
 * @param {object} pack
 * @param {string} hex - payload hex
 * @param {string} P2
 */
function parseP1_7A(pack, hex, P2) {
    // Format: P1 P2 [TLVs ...] connId
    if (hex.length < 6) { // at least P1 P2 + connId
        return;
    }

    const connIdHex = hex.slice(hex.length - 2, hex.length);
    const dataHex = hex.slice(4, hex.length - 2); // after P1 P2 until before connId
    const dataBytes = hexToBytes(dataHex);
    const tlvs = parseTlvs(dataBytes);

    // Semantic TLV mapping
    const semantic = {};
    for (let i = 0; i < tlvs.length; i++) {
        const item = tlvs[i];
        switch (item.t) {
            case 0x10:
                semantic.nameHex = item.valueHex;
                break;
            case 0x11:
                semantic.macHex = item.valueHex;
                break;
            case 0x14:
                semantic.snHex = item.valueHex;
                break;
            case 0x15:
                semantic.deviceStatusHex = item.valueHex;
                break;
            default:
                break;
        }
    }

    const blePacket = {
        P1: '7A',
        P2: P2,
        P3: '',
        cmd: null,
        length: dataBytes.length,
        dataHex: dataHex,
        data: Array.from(dataBytes),
        connId: connIdHex,
        tlv: tlvs,
        semantic: semantic
    };

    pack.ble = blePacket;
    logInfo('vgBleProtocol ble packet (P1=7A):', JSON.stringify(blePacket));

    // When P1 == 0x7A, treat as setConfig response; store semantic via dxMap for async return
    const vgMap = dxMap.get("dxVgBleMap");
    const token = vgMap.get("slave_setConfig_token");
    if (token) {
        vgMap.put("slave_setConfig_result", {
            token: token,
            semantic: semantic
        });
    }
}

export default dxVgBle